<!doctype html>
<!-- Based on layout/style/test/test_transitions_per_property.html in Gecko -->
<title>Per-property transitions tests</title>
<script src=http://w3c-test.org/resources/testharness.js></script>
<script src=http://w3c-test.org/resources/testharnessreport.js></script>
<script src="animation_utils.js"></script>
<!--
  fixed-height container so percentage heights compute to different
  (i.e., nonzero) values
  fixed-width container so that percentages for margin-top and
  margin-bottom are all relative to the same size container (rather than
  one that depends on whether we're tall enough to need a scrollbar)

  Use a 20px font size and line-height so that percentage line-height
  and vertical-align doesn't accumulate rounding error.
  -->
<div id=tests style="height: 50px; width: 300px; font-size: 20px; line-height: 20px">
</div>
<div id=log></div>
<script>
"use strict";

// FIXME: Tolerances for assert_approx_equals() are more or less arbitrary.
// I tend to pick them just about low enough that all the browsers I'm testing
// in pass, as long as they're not unreasonable (e.g., a pixel or so off).
// Similarly, in many cases I use assert_in_array(), which is often much less
// reasonable because it prohibits slight variation.  My goal is just to stop
// current browsers from failing due to behavior that nothing prohibits, but
// random changes to those browsers might cause spurious test failures if they
// happen to hit other behavior that's not actually prohibited but that my
// test doesn't allow for.  Solution: define precise behavior, please.

var prefixed_transition = "transition" in document.body.style ? "transition"
  : "msTransition" in document.body.style ? "msTransition"
  : "MozTransition" in document.body.style ? "MozTransition"
  : "webkitTransition" in document.body.style ? "webkitTransition"
  : "OTransition" in document.body.style ? "OTransition"
  : undefined;

var hyph_prefix = {
  transition: "",
  msTransition: "-ms-",
  MozTransition: "-moz-",
  webkitTransition: "-webkit-",
  OTransition: "-o-",
}[prefixed_transition];

if (prefixed_transition === undefined) {
  throw "Transitions not supported?";
}

setup({timeout: 9000});

add_completion_callback(function() {
  document.body.removeChild(div.parentNode);
});

var FUNC_NEGATIVE = "cubic-bezier(0.25, -2, 0.75, 1)";

// True longhand properties.
var CSS_TYPE_LONGHAND = 0;

// True shorthand properties.
var CSS_TYPE_TRUE_SHORTHAND = 1;

// Properties that we handle as shorthands but were longhands either in
// the current spec or earlier versions of the spec.
var CSS_TYPE_SHORTHAND_AND_LONGHAND = 2;

var prereqs = {
  "border-bottom-width": { "border-bottom-style": "solid" },
  "border-left-width": { "border-left-style": "solid" },
  "border-right-width": { "border-right-style": "solid" },
  "border-top-width": { "border-top-style": "solid" },
  "bottom": { "position": "relative" },
  "left": { "position": "relative" },
  "outline-width": { "outline-style": "solid" },
  "right": { "position": "relative" },
  "top": { "position": "relative" },
};

var supported_properties = {
    // From the transitions spec
    "background-color": [ test_color_transition ],
    "background-position": [ test_background_position_transition,
                             // FIXME: We don't currently test clamping,
                             // why not?
                             /* test_length_percent_pair_unclamped */ ],
    "border-bottom-color": [ test_color_transition,
                             test_border_color_transition ],
    "border-bottom-width": [ test_length_transition,
                             test_length_clamped ],
    "border-left-color": [ test_color_transition,
                           test_border_color_transition ],
    "border-left-width": [ test_length_transition,
                           test_length_clamped ],
    "border-right-color": [ test_color_transition,
                            test_border_color_transition ],
    "border-right-width": [ test_length_transition,
                            test_length_clamped ],
    "border-spacing": [ test_length_pair_transition,
                        test_length_pair_transition_clamped ],
    "border-top-color": [ test_color_transition,
                          test_border_color_transition ],
    "border-top-width": [ test_length_transition,
                           test_length_clamped ],
    "bottom": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_unclamped, test_percent_unclamped ],
    "clip": [ test_rect_transition ],
    "color": [ test_color_transition ],
    "font-size": [ test_length_transition, test_percent_transition,
                   test_length_percent_calc_transition,
                   test_length_clamped, test_percent_clamped ],
    // FIXME: Transition not yet defined
    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=14613
    //"font-weight": [ test_font_weight ],
    "height": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_clamped, test_percent_clamped ],
    "left": [ test_length_transition, test_percent_transition,
              test_length_percent_calc_transition,
              test_length_unclamped, test_percent_unclamped ],
    "letter-spacing": [ test_length_transition, test_length_unclamped ],
    "line-height": [ test_length_transition, test_percent_transition,
                     test_length_clamped, test_percent_clamped ],
    "margin-bottom": [ test_length_transition, test_percent_transition,
                       test_length_percent_calc_transition,
                       test_length_unclamped, test_percent_unclamped ],
    "margin-left": [ test_length_transition, test_percent_transition,
                     test_length_percent_calc_transition,
                     test_length_unclamped, test_percent_unclamped ],
    "margin-right": [ test_length_transition, test_percent_transition,
                      test_length_percent_calc_transition,
                      test_length_unclamped, test_percent_unclamped ],
    "margin-top": [ test_length_transition, test_percent_transition,
                    test_length_percent_calc_transition,
                    test_length_unclamped, test_percent_unclamped ],
    "max-height": [ test_length_transition, test_percent_transition,
                    test_length_percent_calc_transition,
                    test_length_clamped, test_percent_clamped ],
    "max-width": [ test_length_transition, test_percent_transition,
                   test_length_percent_calc_transition,
                   test_length_clamped, test_percent_clamped ],
    "min-height": [ test_length_transition, test_percent_transition,
                    test_length_percent_calc_transition,
                    test_length_clamped, test_percent_clamped ],
    "min-width": [ test_length_transition, test_percent_transition,
                   test_length_percent_calc_transition,
                   test_length_clamped, test_percent_clamped ],
    "opacity" : [ test_float_zeroToOne_transition,
                  // opacity is clamped in computed style
                  // (not parsing/interpolation)
                  test_float_zeroToOne_clamped ],
    "outline-color": [ test_color_transition ],
    "outline-offset": [ test_length_transition, test_length_unclamped ],
    "outline-width": [ test_length_transition, test_length_clamped ],
    "padding-bottom": [ test_length_transition, test_percent_transition,
                        test_length_percent_calc_transition,
                        test_length_clamped, test_percent_clamped ],
    "padding-left": [ test_length_transition, test_percent_transition,
                      test_length_percent_calc_transition,
                      test_length_clamped, test_percent_clamped ],
    "padding-right": [ test_length_transition, test_percent_transition,
                       test_length_percent_calc_transition,
                       test_length_clamped, test_percent_clamped ],
    "padding-top": [ test_length_transition, test_percent_transition,
                     test_length_percent_calc_transition,
                     test_length_clamped, test_percent_clamped ],
    "right": [ test_length_transition, test_percent_transition,
               test_length_percent_calc_transition,
               test_length_unclamped, test_percent_unclamped ],
    "text-indent": [ test_length_transition, test_percent_transition,
                     test_length_percent_calc_transition,
                     test_length_unclamped, test_percent_unclamped ],
    "text-shadow": [ test_shadow_transition ],
    "top": [ test_length_transition, test_percent_transition,
             test_length_percent_calc_transition,
             test_length_unclamped, test_percent_unclamped ],
    "vertical-align": [ test_length_transition, test_percent_transition,
                        test_length_percent_calc_transition,
                        test_length_unclamped, test_percent_unclamped ],
    "visibility": [ test_visibility_transition ],
    "width": [ test_length_transition, test_percent_transition,
               test_length_percent_calc_transition,
               test_length_clamped, test_percent_clamped ],
    "word-spacing": [ test_length_transition, test_length_unclamped ],
    "z-index": [ test_zindex_transition, test_pos_integer_or_auto_transition ],
    // Missing crop and zoom, because they're noted as nonstandard in the
    // spec.  Missing grid-* because . . . why?

    // Many other properties in the original Gecko test are not required by
    // the spec, so not included here
};

var tests = document.getElementById("tests");

// FIXME: Test that non-animatable properties are really not animatable?

(function() {
var delayed_funcs = [];

window.add_delayed_func = function(f) {
  delayed_funcs.push(f);
};

setTimeout(function() {
  delayed_funcs.forEach(function(f) { f() });
}, 100);
})();

/**
 * Do 3600-second linear transitions with -900 second transition delay and
 * linear timing function so that we can expect the transition to be one
 * quarter of the way through the value space right after changing the
 * property.  We delay by 100 ms, but this should be small enough to be no big
 * deal.
 */
function create_test_div() {
  var div = document.createElement("div");
  tests.appendChild(div);
  div.style.setProperty(hyph_prefix + "transition-duration", "3600s");
  div.style.setProperty(hyph_prefix + "transition-delay", "-900s");
  div.style.setProperty(hyph_prefix + "transition-timing-function", "linear");
  return div;
}

for (var prop in supported_properties) {
  var tinfo = supported_properties[prop];

  for (var idx in tinfo) {
    var div = create_test_div();

    if (prop in prereqs) {
      for (var prereq in prereqs[prop]) {
        div.style.setProperty(prereq, prereqs[prop][prereq]);
      }
    }

    tinfo[idx](prop, div, getComputedStyle(div));
  }
}

/**
 * Given a CSS color /input/, return an array of numbers [red, green, blue,
 * alpha].  red, green, and blue are from 0 to 255, and alpha is from 0 to 1.
 * Does not handle all possible colors, only the sorts that come up in
 * testing.
 */
function parse_color(input) {
  var rgb_match = /^rgb\((.*), ?(.*), ?(.*)\)$/.exec(input);
  if (rgb_match) {
    return [Number(rgb_match[1]), Number(rgb_match[2]), Number(rgb_match[3]),
      1];
  }
  var rgba_match = /^rgba\((.*), ?(.*), ?(.*), ?(.*)\)$/.exec(input);
  if (rgba_match) {
    return [Number(rgba_match[1]), Number(rgba_match[2]),
      Number(rgba_match[3]), Number(rgba_match[4])];
  }
  var hex_match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(input);
  if (hex_match) {
    return [parseInt(hex_match[1], 16), parseInt(hex_match[2], 16),
      parseInt(hex_match[3], 16), 1];
  }
  // Support only CSS2.1 colors for now.  Hopefully tests only use those.
  var keyword_match = {
    maroon:  [0x80, 0x00, 0x00, 1],
    red:     [0xff, 0x00, 0x00, 1],
    orange:  [0xff, 0xa5, 0x00, 1],
    yellow:  [0xff, 0xff, 0x00, 1],
    olive:   [0x80, 0x80, 0x00, 1],
    purple:  [0x80, 0x00, 0x80, 1],
    fuchsia: [0xff, 0x00, 0xff, 1],
    white:   [0xff, 0xff, 0xff, 1],
    lime:    [0x00, 0xff, 0x00, 1],
    green:   [0x00, 0x80, 0x00, 1],
    navy:    [0x00, 0x00, 0x80, 1],
    blue:    [0x00, 0x00, 0xff, 1],
    aqua:    [0x00, 0xff, 0xff, 1],
    teal:    [0x00, 0x80, 0x80, 1],
    black:   [0x00, 0x00, 0x00, 1],
    silver:  [0xc0, 0xc0, 0xc0, 1],
    gray:    [0x80, 0x80, 0x80, 1],
  }[input.toLowerCase().trim()];
  if (keyword_match) {
    return keyword_match;
  }
  return null;
}

/**
 * Asserts that the strings /actual/ and /expected/ both parse to the same
 * color.  See parse_color().
 */
function assert_colors_equiv(actual, expected, desc) {
  var actual_parsed = parse_color(actual);
  assert_not_equals(actual_parsed, null,
    desc + " unrecognized color " + actual);
  var expected_parsed = parse_color(expected);
  assert_not_equals(expected_parsed, null,
    desc + " test bug: expected a color " + format_value(expected)
    + " that's not a recognized color");

  var expected = "rgba(" + expected_parsed.join(", ") + ")";

  // IE10 Developer Preview and Opera Next 12.00 alpha are off by one for some
  // of the rgb coordinates sometimes.
  assert_approx_equals(actual_parsed[0], expected_parsed[0], 1,
    desc + " incorrect red coordinate; expected " + expected
    + " but got " + format_value(actual));
  assert_approx_equals(actual_parsed[1], expected_parsed[1], 1,
    desc + " incorrect green coordinate; expected " + expected
    + " but got " + format_value(actual));
  assert_approx_equals(actual_parsed[2], expected_parsed[2], 1,
    desc + " incorrect blue coordinate; expected " + expected
    + " but got " + format_value(actual));
  // Chrome 18 dev outputs an opacity of 0.75 as 0.746094.
  assert_approx_equals(actual_parsed[3], expected_parsed[3], 0.01,
    desc + " incorrect alpha coordinate; expected " + expected
    + " but got " + format_value(actual));
}

/**
 * Asserts that the strings /actual/ and /expected/ both parse to the same
 * shadow list, as used for text-shadow.  (The extra arguments used for
 * box-shadow are not handled.)  Does not handle all possible shadows, only
 * those that came up in testing.  E.g., accepts only pixel lengths and
 * doesn't tolerate unusual whitespace.
 */
function assert_shadows_equiv(actual, expected, desc) {
  var parse_shadows = function(input) {
    // We want to split on commas to get a list of shadows, so first get rid of
    // the ones inside parentheses.
    input = input
      .replace(/\(([^,)]*),([^,)]*),([^,)]*)\)/g, "($1@$2@$3)")
      .replace(/\(([^,)]*),([^,)]*),([^,)]*),([^,)]*)\)/g, "($1@$2@$3@$4)");
    var bits = input.split(",");
    var ret = [];
    for (var i = 0; i < bits.length; i++) {
      var subbits = bits[i].replace(/@/g, ",").split(/(px|\))/);
      var shadow = {lengths: []};
      for (var j = 0; j < subbits.length; j++) {
        var subbit = subbits[j].trim();
        if (subbits[j + 1] == ")") {
          subbit += ")";
        }
        if (subbit == "px" || subbit == ")" || subbit == "") {
          continue;
        }
        if (!isNaN(Number(subbit))) {
          if (shadow.lengths.length >= 3) {
            return null;
          }
          shadow.lengths.push(Number(subbit));
        } else {
          if ("color" in shadow) {
            return null;
          }
          shadow.color = subbit;
        }
      }
      while (shadow.lengths.length < 3) {
        shadow.lengths.push(0);
      }
      ret.push(shadow);
    }
    return ret;
  }

  var actual_parsed = parse_shadows(actual);
  assert_not_equals(actual_parsed, null,
    desc + " unrecognized shadow format " + format_value(actual));
  var expected_parsed = parse_shadows(expected);
  assert_not_equals(expected_parsed, null,
    desc + " test bug: expected a shadow " + format_value(expected)
    + " that's not a valid shadow format");

  var expected = expected_parsed.map(function(shadow) {
    return "rgba(" + parse_color(shadow.color).join(", ") + ") "
      + shadow.lengths.join("px ") + "px";
  }).join(", ");

  var expected_actual_string = "; expected "
    + format_value(expected) + " but got "
    + format_value(actual);

  assert_equals(actual_parsed.length, expected_parsed.length,
    desc + " wrong number of shadows" + expected_actual_string);

  for (var i = 0; i < actual_parsed.length; i++) {
    assert_colors_equiv(actual_parsed[i].color, expected_parsed[i].color,
      desc + " wrong color in shadow " + i + expected_actual_string);
    for (var j = 0; j < 3; j++) {
      // Chrome 18 dev rounds the lengths to the nearest pixel.
      assert_approx_equals(actual_parsed[i].lengths[j],
        expected_parsed[i].lengths[j], 0.51,
        desc + " length " + j + " is wrong in shadow " + i
        + expected_actual_string);
    }
  }
}

function do_transition_test(desc, prop, div,
                            before_flush, after_flush, after_delay) {
  var t = async_test(desc + " for '" + prop + "'");
  t.step(function() {
    div.style.setProperty(hyph_prefix + "transition-property", "none");
    before_flush();
    div.style.setProperty(hyph_prefix + "transition-property", prop);

    // FIXME: The behavior here is not really defined.  This seems to work in
    // browsers in practice.  https://www.w3.org/Bugs/Public/show_bug.cgi?id=16016
    var x = getComputedStyle(tests).color;

    after_flush();
  });

  add_delayed_func(function() {
    t.step(after_delay);
    t.done();
  });
}

function test_length_transition(prop, div, cs) {
  do_transition_test("Length transition", prop, div,
    function() {
      div.style.setProperty(prop, "4px");
      assert_equals(cs.getPropertyValue(prop), "4px",
                    "computed value before transition");
    },
    function() { div.style.setProperty(prop, "12px") },
    function() {
      assert_equals(cs.getPropertyValue(prop), "6px",
                    "computed value after transition");
    }
  );
}

function test_length_clamped(prop, div, cs) {
  test_length_clamped_or_unclamped(true, prop, div, cs);
}

function test_length_unclamped(prop, div, cs) {
  test_length_clamped_or_unclamped(false, prop, div, cs);
}

function test_length_clamped_or_unclamped(is_clamped, prop, div, cs) {
  do_transition_test("Length clamping test", prop, div,
    function() {
      div.style.setProperty(hyph_prefix + "transition-timing-function", FUNC_NEGATIVE);
      div.style.setProperty(prop, "0px");
      assert_equals(cs.getPropertyValue(prop), "0px",
         "flush before clamping test");
    },
    function() { div.style.setProperty(prop, "100px") },
    function() {
      (is_clamped ? assert_equals : assert_not_equals)(
        cs.getPropertyValue(prop), "0px",
        "clamping of negatives");
    }
  );
}

// Test using float values in the range [0, 1] (e.g. opacity)
function test_float_zeroToOne_transition(prop, div, cs) {
  do_transition_test("Float transition in range [0, 1]", prop, div,
    function() {
      div.style.setProperty(prop, "0.3");
      // Chrome 18 dev is a little over 1e-8 off when reserializing.
      assert_approx_equals(Number(cs.getPropertyValue(prop)), 0.3, 1e-7,
                           "computed value before transition");
    },
    function() { div.style.setProperty(prop, "0.8") },
    function() {
      // Opera Next 12.00 alpha rounds to two figures, so allow 0.0051 error.
      assert_approx_equals(Number(cs.getPropertyValue(prop)), 0.425,
                           0.0051, "computed value after transition");
    }
  );
}

function test_float_zeroToOne_clamped(prop, div, cs) {
  test_float_zeroToOne_clamped_or_unclamped(true, prop, div, cs);
}

function test_float_zeroToOne_clamped_or_unclamped(is_clamped, prop, div, cs) {
  do_transition_test("Float clamping test", prop, div,
    function() {
      div.style.setProperty(hyph_prefix + "transition-timing-function", FUNC_NEGATIVE);
      div.style.setProperty(prop, "0");
      assert_equals(cs.getPropertyValue(prop), "0",
                    "flush before clamping test");
    },
    function() { div.style.setProperty(prop, "1") },
    function() {
      (is_clamped ? assert_equals : assert_not_equals)(
        cs.getPropertyValue(prop), "0",
        "clamping of negatives");
    }
  );
}

function test_percent_transition(prop, div, cs) {
  var a, b, value_a, value_b;
  do_transition_test("Percent transition", prop, div,
    function() {
      div.style.setProperty(prop, "25%");
      value_a = cs.getPropertyValue(prop);
      a = parseFloat(value_a);
      div.style.setProperty(prop, "75%");
      value_b = cs.getPropertyValue(prop);
      b = parseFloat(value_b);
      assert_not_equals(b, a,
        "different percentages must give different computed values");
    },
    function() { div.style.setProperty(prop, "25%") },
    function() {
      var result = cs.getPropertyValue(prop);
      // Chrome 18 dev clamps font-size to whole pixels, so need to allow
      // 0.51.  Opera Next 12.00 alpha gives 187.5px instead of 186px for
      // width; I'm calling this excessive and failing it.
      assert_approx_equals(parseFloat(result), (3*b + a)/4, 0.51,
        "computed style must be a quarter of the way between " + value_b + " and " + value_a);
      assert_false(isNaN(parseFloat(result)),
         "percent must compute to number");
    }
  );
}

function test_percent_clamped(prop, div, cs) {
  test_percent_clamped_or_unclamped(true, prop, div, cs);
}

function test_percent_unclamped(prop, div, cs) {
  test_percent_clamped_or_unclamped(false, prop, div, cs);
}

function test_percent_clamped_or_unclamped(is_clamped, prop, div, cs) {
  var zero_val;
  do_transition_test("Percent clamping test", prop, div,
    function() {
      div.style.setProperty(hyph_prefix + "transition-timing-function", FUNC_NEGATIVE);
      div.style.setProperty(prop, "0%");
      zero_val = cs.getPropertyValue(prop);
    },
    function() { div.style.setProperty(prop, "150%") },
    function() {
      (is_clamped ? assert_equals : assert_not_equals)(
        cs.getPropertyValue(prop), zero_val,
        "clamping of negatives");
    }
  );
}

// FIXME: This tests calc() too, in the source file, but handling of calc() is
// not defined by the transition spec.
function test_length_percent_calc_transition(prop, div, cs) {
  var a, b, c;
  do_transition_test("Length-percent transition", prop, div,
    function() {
      div.style.setProperty(prop, "0%");
      var value_a = cs.getPropertyValue(prop);
      a = parseFloat(value_a);
      div.style.setProperty(prop, "100%");
      var value_b = cs.getPropertyValue(prop);
      b = parseFloat(value_b);
      assert_not_equals(b, a,
        "different percentages (" + value_a + " and " + value_b +
        ") must give different computed values");

      div.style.setProperty(prop, "100px");
      var value_c = cs.getPropertyValue(prop);
      c = parseFloat(value_c);
      assert_equals(c, 100, "Setting to 100px must give that computed value");

      div.style.setProperty(prop, "50%");
      var v1_value = cs.getPropertyValue(prop);
      // Everyone seems to get this test exactly right, so no need for
      // assert_approx_equals.
      assert_equals(parseFloat(v1_value), (a + b)/2,
        "computed value before transition : '" +
        v1_value + "' must be halfway " +
        "between '" + value_a + "' + and '" + value_b + "'.");
    },
    function() { div.style.setProperty(prop, "200px") },
    function() {
      var v2_value = cs.getPropertyValue(prop);
      // Chrome 18 dev rounds font-size to whole pixels, so it needs 0.51
      // tolerance.
      assert_approx_equals(parseFloat(v2_value), 0.75*(a + b)/2 + 0.25*(2*c),
        0.51,
        "computed value after transition must be a quarter of the way "
        + "between 50% (" + (a + b)/2 + "px) and 200px");
    }
  );
}

function test_color_transition(prop, div, cs) {
  do_transition_test("Color transition", prop, div,
    function() {
      div.style.setProperty(prop, "rgb(255, 28, 0)");
      assert_colors_equiv(cs.getPropertyValue(prop), "rgb(255, 28, 0)",
                          "computed value before transition");
    },
    function() { div.style.setProperty(prop, "rgb(75, 84, 128)") },
    function() {
      assert_colors_equiv(cs.getPropertyValue(prop), "rgb(210, 42, 32)",
        "computed value after transition");
    }
  );

  // We have more tests, so create more test divs.
  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Color transition to currentColor", prop, div,
      function() {
        div.style.setProperty(prop, "rgb(128, 64, 0)");
        // We might be testing the 'color' property, so to test currentColor, we
        // need a parent div.
        var parentDiv = document.createElement("div");
        tests.appendChild(parentDiv);
        parentDiv.appendChild(div);
        parentDiv.style.setProperty("color", "rgb(0, 0, 128)");
        assert_colors_equiv(cs.getPropertyValue(prop), "rgb(128, 64, 0)",
                            "computed value before transition");
      },
      function() { div.style.setProperty(prop, "currentColor") },
      function() {
        assert_colors_equiv(cs.getPropertyValue(prop), "rgb(96, 48, 32)",
                            "computed value after transition");
      }
    );
  })();

  // FIXME: Omitted bounds-checking, because it's legitimate for
  // implementations to support colors outside of sRGB.
}

function test_border_color_transition(prop, div, cs) {
  do_transition_test("Border color transition", prop, div,
    function() {
      div.style.setProperty(prop, "rgb(128, 64, 0)");
      div.style.setProperty("color", "rgb(0, 0, 128)");
      assert_colors_equiv(cs.getPropertyValue(prop), "rgb(128, 64, 0)",
                          "computed value before transition");
    },
    function() { div.style.removeProperty(prop) },
    function() {
      assert_colors_equiv(cs.getPropertyValue(prop), "rgb(96, 48, 32)",
                          "computed value after transition");
    }
  );
}

function test_shadow_transition(prop, div, cs) {
  var spread_str = (prop == "box-shadow") ? " 0px" : "";

  do_transition_test("Shadow transition", prop, div,
    function() {
      div.style.setProperty(prop, "none");
      // IE10 Developer Preview returns #000000.  I fail this because
      // "text-shadow: #000000" is a parse error per spec.
      assert_equals(cs.getPropertyValue(prop), "none",
        "computed value before transition");
    },
    function() { div.style.setProperty(prop, "4px 8px 3px red") },
    function() {
      assert_shadows_equiv(cs.getPropertyValue(prop),
        "rgba(255, 0, 0, 0.25) 1px 2px 0.75px" + spread_str,
        "computed value after transition");
    }
  );

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Two-shadow transition", prop, div,
      function() {
        div.style.setProperty(prop, "#038000 4px 4px, 2px 2px blue");
        assert_shadows_equiv(cs.getPropertyValue(prop),
          "rgb(3, 128, 0) 4px 4px 0px" + spread_str + ", "
            + "rgb(0, 0, 255) 2px 2px 0px" + spread_str,
          "computed value before transition");
      },
      function() { div.style.setProperty(prop, "8px 8px 8px red") },
      function() {
        var computed = cs.getPropertyValue(prop);
        assert_shadows_equiv(computed,
          "rgb(66, 96, 0) 5px 5px 2px" + spread_str
          + ", rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px" + spread_str,
          'computed value after transition');
      }
    );
  })();

  // FIXME: There were some box-shadow-related tests in the original file that
  // weren't ported, because we don't test box-shadow right now.

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Shadow transition from color to no color", prop, div,
      function() {
        div.style.setProperty(prop, "8px 8px 8px red");
        div.style.setProperty("color", "lime");
        assert_shadows_equiv(cs.getPropertyValue(prop), "8px 8px 8px red",
          "computed value before transition");
      },
      function() { div.style.setProperty(prop, "2px 2px 2px") },
      function() {
        assert_shadows_equiv(cs.getPropertyValue(prop),
          "rgb(191.25, 63.75, 0) 6.5px 6.5px 6.5px",
          "computed value after transition");
      }
    );
  })();

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Shadow transition from no color to color", prop, div,
      function() {
        div.style.setProperty(prop, "8px 8px 8px");
        div.style.setProperty("color", "red");
        assert_shadows_equiv(cs.getPropertyValue(prop), "8px 8px 8px red",
          "computed value before transition");
      },
      function() { div.style.setProperty(prop, "2px 2px 2px lime") },
      function() {
        assert_shadows_equiv(cs.getPropertyValue(prop),
          "rgb(191.25, 63.75, 0) 6.5px 6.5px 6.5px",
          "computed value after transition");
      }
    );
  })();

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Shadow transition with no color", prop, div,
      function() {
        div.style.setProperty(prop, "2px 2px 2px");
        div.style.setProperty("color", "red");
        assert_shadows_equiv(cs.getPropertyValue(prop), "2px 2px 2px red",
          "computed value before transition");
      },
      function() {div.style.setProperty(prop, "6px 14px 10px") },
      function() {
        assert_shadows_equiv(cs.getPropertyValue(prop),
          "3px 5px 4px red",
          "computed value after transition");
      }
    );
  })();

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Shadow clamping", prop, div,
      function() {
        div.style.setProperty(hyph_prefix + "transition-timing-function", FUNC_NEGATIVE);
        div.style.setProperty(prop, "0px 0px 0px black");
        assert_shadows_equiv(cs.getPropertyValue(prop), "0px 0px 0px black",
                             "computed value before transition");
      },
      function() { div.style.setProperty(prop, "10px 10px 10px black") },
      function() {
        // I didn't actually compute that 6.83px is correct here.  But that's
        // what all implementations seem to yield, certainly to within my 0.5px
        // accuracy.  No need to be precise.
        assert_shadows_equiv(cs.getPropertyValue(prop),
                             "-6.83px -6.83px 0px black",
                             "clamping of negatives");
      }
    );
  })();
}

function test_zindex_transition(prop, div, cs) {
  do_transition_test("Z-index transition", prop, div,
    function() {
      div.style.setProperty(prop, "4");
      assert_equals(cs.getPropertyValue(prop), "4",
                    "computed value before transition");
    },
    function() { div.style.setProperty(prop, "-14") },
    function() {
      // The distance from 4 to -14 is 18, so it will be 4 - 18/4 = -0.5.  The
      // spec says the conversion to integer is via floor(), so it should
      // become -1.  This should be true even if the value isn't quite -0.5,
      // as long as it's less than 0.5 off.
      assert_equals(cs.getPropertyValue(prop), "-1",
                    "computed value after transition");
    }
  );

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Z-index transition to auto", prop, div,
      function() {
        div.style.setProperty(prop, "6");
        assert_equals(cs.getPropertyValue(prop), "6",
                      "computed value before transition");
      },
      function() { div.style.setProperty(prop, "auto") },
      function() {
        // FIXME: https://www.w3.org/Bugs/Public/show_bug.cgi?id=16265
        // We want a bunch more tests once this is specced, to handle corner
        // cases.  At least testing from auto, and (if it's not specced to
        // jump) expected values of 0 when transitioning both to and from.
        //assert_equals(cs.getPropertyValue(prop), "2",
        //              "computed value after transition");
      }
    );
  })();

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Z-index transition to exact value", prop, div,
      function() {
        div.style.setProperty(prop, "-4");
        assert_equals(cs.getPropertyValue(prop), "-4",
                      "computed value before transition");
      },
      function() { div.style.setProperty(prop, "8") },
      function() {
        // There's a delay of a handful of milliseconds here, so the computed
        // value should be just above -1.  This should be clamped to -1.
        assert_equals(cs.getPropertyValue(prop), "-1",
                      "computed value after transition");
      }
    );
  })();

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Z-index clamping test", prop, div,
      function() {
        div.style.setProperty(hyph_prefix + "transition-timing-function", FUNC_NEGATIVE);
        div.style.setProperty(prop, "0");
        assert_equals(cs.getPropertyValue(prop), "0",
                      "computed value before transition");
      },
      function() { div.style.setProperty(prop, "100") },
      function() {
        assert_not_equals(cs.getPropertyValue(prop), "0",
                          "computed value after transition");
      }
    );
  })();
}

function test_pos_integer_or_auto_transition(prop, div, cs) {
  // This is redundant with test_zindex_transition.  In the original source
  // file it was used for column-count as well, but that's not currently
  // transitioned per spec.
}

function test_length_pair_transition(prop, div, cs) {
  do_transition_test("Length pair transition", prop, div,
    function() {
      div.style.setProperty(prop, "4px 6px");
      assert_equals(cs.getPropertyValue(prop), "4px 6px",
                    "computed value before transition");
    },
    function() { div.style.setProperty(prop, "12px 10px") },
    function() {
      // Browsers all seem to round the computed values properly here, so no
      // need for assert_approx_equals.
      assert_equals(cs.getPropertyValue(prop), "6px 7px",
                    "computed value after transition");
    }
  );
}

function test_length_pair_transition_clamped(prop, div, cs) {
  do_transition_test("Length pair clamping test", prop, div,
    function() {
      div.style.setProperty(hyph_prefix + "transition-timing-function", FUNC_NEGATIVE);
      div.style.setProperty(prop, "0px 0px");
      assert_equals(cs.getPropertyValue(prop), "0px 0px",
                    "computed value before transition");
    },
    function() { div.style.setProperty(prop, "30px 50px") },
    function() {
      // Opera Next 12.00 alpha serializes border-spacing here as "0px", which
      // is at least as correct as "0px 0px", so we'll give it a pass.
      assert_in_array(cs.getPropertyValue(prop), ["0px 0px", "0px"],
                    "computed value after transition");
    }
  );
}

function test_rect_transition(prop, div, cs) {
  // FIXME: Chrome 19 dev serializes rect() with no spaces.  This is probably
  // wrong, but CSSOM is not totally clear, so I have to pass it for now.
  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16266
  var re = /^rect\((-?[0-9.]+)px,? (-?[0-9.]+)px,? (-?[0-9.]+)px,? (-?[0-9.]+)px\)$/;
  do_transition_test("Rectangle transition", prop, div,
    function() {
      div.style.setProperty(prop, "rect(4px, 16px, 12px, 6px)");
      assert_in_array(cs.getPropertyValue(prop),
                    ["rect(4px, 16px, 12px, 6px)", "rect(4px 16px 12px 6px)"],
                    "computed value before transition");
    },
    function() { div.style.setProperty(prop, "rect(0px, 4px, 4px, 2px)") },
    function() {
      // IE10 Developer Preview is about 0.01px off, so we need to test all
      // the components individually, not as a string.  Chrome 19 dev
      // apparently rounds *down* to the nearest pixel, which is quite dodgy
      // but I'm not going to say it's actually prohibited.  So I allow plus
      // or minus one pixel.
      var val = cs.getPropertyValue(prop);
      assert_regexp_match(val, re, "computed value after transition");
      var desc = " component of computed value after transition"
        + " (expected \"rect(3px, 13px, 10px, 5px)\", got \"" + val + "\")";
      var matches = re.exec(val);
      assert_approx_equals(Number(matches[1]), 3, 1, "first" + desc);
      assert_approx_equals(Number(matches[2]), 13, 1, "second" + desc);
      assert_approx_equals(Number(matches[3]), 10, 1, "third" + desc);
      assert_approx_equals(Number(matches[4]), 5, 1, "fourth" + desc);
    }
  );

  // FIXME: There were some tests for transitions to/from auto in the original
  // file, but that's not defined right now.
  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=15844

  (function() {
    var div = create_test_div();
    var cs = getComputedStyle(div);
    do_transition_test("Rectangle clamping test", prop, div,
      function() {
        div.style.setProperty(hyph_prefix + "transition-timing-function", FUNC_NEGATIVE);
        div.style.setProperty(prop, "rect(-10px, 30px, 0px, 0px)");
        assert_in_array(cs.getPropertyValue(prop),
                  ["rect(-10px, 30px, 0px, 0px)", "rect(-10px 30px 0px 0px)"],
                  "computed value before transition");
      },
      function() {
        div.style.setProperty(prop, "rect(0px, 40px, 10px, 10px)");
      },
      function() {
        var val = cs.getPropertyValue(prop);
        assert_regexp_match(val, re, "computed value after transition");
        var desc = " component of computed value after transition must be "
          + "less than we started with "
          + "(expected < \"rect(-10px, 30px, 0px, 0px)\", "
          + "got " + format_value(val) + ")";
        var matches = re.exec(val);
        assert_true(Number(matches[1]) < -10, "first" + desc);
        assert_true(Number(matches[2]) < 30, "second" + desc);
        assert_true(Number(matches[3]) < 0, "third" + desc);
        assert_true(Number(matches[4]) < 0, "fourth" + desc);
      }
    );
  })();
}

function test_visibility_transition(prop, div, cs) {
}

function test_background_position_transition(prop, div, cs) {
}
</script>
<!-- vim: set tw=78 sw=2 et ts=2 : -->
